/*
 *  $Id: Main.java 3 2008-11-18 20:02:30Z Nathaniel.Waisbrot $
    Copyright (C) 2008  Nathaniel Waisbrot

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package termint;

import java.io.File;
import java.io.IOException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

import termint.brains.FeatureWatcher;
import termint.brains.MessageWatcher;
import termint.brains.PriceWatcher;
import termint.brains.RoomWatcher;
import termint.brains.SoundDecoder;
import termint.brains.StatsWatcher;
import termint.gui.GameSelectionDialog;
import termint.gui.LocalShell;
import termint.gui.MainPalate;
import termint.gui.vt.VT320;
import termint.gui.vt.VTElementWatcher;
import termint.gui.window.LevelNotes;
import termint.gui.window.PriceNotes;
import termint.gui.window.TextWindow;
import termint.gui.window.TilesWindow;
import termint.util.Pair;

/**
 * Class containing the initial entry point and the main REPL
 * 
 * Also contains some static constants referenced by other classes
 * 
 * @author Nathaniel Waisbrot
 *
 */
public class Main {
	
	/** OS-dependent character separating path elements */
	public static final String dirSeparator = System.getProperty("file.separator");
	
	/** Directory where user files can be stored */
	public static final String localDir = System.getProperty("user.home")+dirSeparator+".nethack_termint"+dirSeparator;

	public static final boolean development = true;
	
	/**
	 * Program entry point.  Ignores args for now.
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			makeDirs();
			Main m = new Main();
			try {
				m.run();
			} catch (Throwable t) {
				t.printStackTrace();
			}
			m.dispose();
			System.exit(0);
		} catch (IOException e) {
			// DEBUG
			e.printStackTrace();
			System.err.println("DEBUG: IOException in main");
		}
	}
	
	/**
	 * Ensure that {@link Main#localDir} is present in the filesystem
	 * @throws IOException if the directory does not exist and cannot be created
	 */
	private static void makeDirs() throws IOException {
		File dir = new File(localDir);
		if (dir.mkdir()   // made OK
		   || (dir.exists() && dir.isDirectory())) {   // already exists
			return;
		} else {
			throw new IOException("Unable to create directory path '"+localDir+"'");
		}
	}
	
	private Pair<LocalShell.Output,LocalShell.Input> pipes;
	private LocalShell sh;
	private VT320 vt;
	private final Display display;
	private TextWindow textWin;
	private TilesWindow tiles;
	
	/**
	 * Clean up SWT objects
	 */
	public void dispose() {
		sh.kill();
		tiles.dispose();
		textWin.dispose();
		display.dispose();
	}
	
	public Main() {
		display = new Display();
	}
	
	/**
	 * Create, configure, and display windows.
	 * Eval the central SWT REPL
	 * @throws IOException 
	 * @throws SWTException
	 */
	public void run() throws IOException, SWTException {
		GameSelectionDialog select = new GameSelectionDialog(null);
		select.open();
		String[] command = select.getSelectedCommand();
		if (command == null)
			return;
		
		// Get the VT up
		vt = new VT320();
		vt.setBufferSize(0);
		
		// Start the shell with nethack and a communications pipe
		String[] wholeCommand = new String[command.length+2];
		System.arraycopy(command, 0, wholeCommand, 2, command.length);
		wholeCommand[0] = "/usr/bin/script";
		wholeCommand[1] = "/dev/null";
//		sh = new LocalShell(new String[]{"/usr/bin/script", "/dev/null", "/usr/games/nethack", "-D"});
		sh = new LocalShell(wholeCommand);
		pipes = sh.start(display);

		// Create some windows
		MainPalate main = new MainPalate(display);
		main.open();
		
		textWin = new TextWindow(main, vt);
		main.addWindow(textWin, "Terminal");

		tiles = new TilesWindow(main, vt);
		main.addWindow(tiles, "Tiles");
		
		LevelNotes notes = new LevelNotes(main);
		main.addWindow(notes, "Notes");
		
		PriceNotes prices = new PriceNotes(main);
		main.addWindow(prices, "Prices");
		
		MessageWatcher messages = new MessageWatcher(vt);
		StatsWatcher statsWatcher = new StatsWatcher(vt);
		VTElementWatcher vteWatcher = new VTElementWatcher();
		vt.addVTChangeListener(vteWatcher);

		SoundDecoder soundDecoder = new SoundDecoder();
		messages.addMessageListener(soundDecoder);
		FeatureWatcher featureWatcher = new FeatureWatcher(statsWatcher, vteWatcher);
		soundDecoder.addListener(featureWatcher);
		featureWatcher.addListener(notes);
		
		RoomWatcher roomWatcher = new RoomWatcher();
		soundDecoder.addListener(roomWatcher);
		roomWatcher.addListener(notes);
		
		tiles.setMessageWatcher(messages);
		statsWatcher.addListener(notes);
		
		PriceWatcher priceWatcher = new PriceWatcher(statsWatcher);
		messages.addMessageListener(priceWatcher);
		priceWatcher.addListener(prices.getPriceModel());
		
		// add some listeners for keypresses
		Listener keyListener = new Listener() {
			public void handleEvent(Event e) {
				if (e.keyCode == e.stateMask) {
					// this is a modifier key being released with no other keys down.  Ignore that.
					return;
				}
				Character c = e.character;
				pipes.b.writeData(c.toString());
				}};
		textWin.setFocus();
		textWin.addListener(SWT.KeyUp, keyListener);
		tiles.addListener(SWT.KeyUp, keyListener);

		// main loop!
		for (;;) {
			if (main.isDisposed() || sh.getThreadGroup() == null)
				break;
			if (!display.readAndDispatch()) {
				String s = pipes.a.readString();
				if (s != null) {
					vt.putString(s);
					textWin.redraw();
					tiles.redraw();
				}
				display.sleep();
			}
		}
	}
}
